package com.abel.bigwater.data

import com.abel.bigmeter.service.DataParam
import com.abel.bigwater.data.jnrq.JnrqDecoder
import com.abel.bigwater.data.jnrq.JnrqTcpHandler
import com.abel.bigwater.data.kente.KenteDecoder
import com.abel.bigwater.data.kente.KenteTcpHandler
import com.abel.bigwater.data.kente.KenteUdpHandler
import com.abel.bigwater.data.mapper.DataMapper
import com.abel.bigwater.data.mapper.DeviceTemplate
import com.abel.bigwater.data.mapper.RtuMapper
import com.abel.bigwater.data.sanchuan.SanchuanDecoder
import com.abel.bigwater.data.sanchuan.SanchuanTcpHandler
import com.abel.bigwater.data.sanchuan.SanchuanUdpHandler
import com.abel.bigwater.msg.RtuMsg
import com.abel.bigwater.msg.RtuMsgType
import com.alibaba.fastjson.JSON
import io.netty.bootstrap.Bootstrap
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.timeout.IdleStateHandler
import org.joda.time.LocalDateTime
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
/*
@ComponentScan(basePackages = arrayOf("com.abel.bigwater.data",
        "com.abel.bigwater.data.mapper",
        "com.abel.bigwater.data.jnrq"))
*/
//@Profile("!test")
open class BwDataAnjiApplication : CommandLineRunner {
    @Value("\${anji.port}")
    var portStr: String? = null

    @Autowired
    var dataMapper: DataMapper? = null

    @Autowired
    var rtuMapper: RtuMapper? = null

    @Autowired
    var deviceTemplate: DeviceTemplate? = null

    @Value("\${anji.firmId}")
    var firmId: String? = null

    @Value("\${anji.firmCode}")
    var firmCode: String? = null

    @Value("\${anji.firmName}")
    var firmName: String? = null


    @Value("\${yujiang.port}")
    var portStrYu: String? = null

    @Value("\${yujiang.firmId}")
    var firmIdYu: String? = null

    @Value("\${yujiang.firmCode}")
    var firmCodeYu: String? = null

    @Value("\${yujiang.firmName}")
    var firmNameYu: String? = null


    @Value("\${kente.port}")
    var portStrKt: String? = null

    @Value("\${kente.firmId}")
    var firmIdKt: String? = null

    @Value("\${kente.firmCode}")
    var firmCodeKt: String? = null

    @Value("\${kente.firmName}")
    var firmNameKt: String? = null

    @Autowired
    var msgHelper: MsgHelper? = null

    /**
     * Callback used to run the bean.
     * @param args incoming main method arguments
     * @throws Exception on error
     */
    @kotlin.ExperimentalStdlibApi
    override fun run(vararg args: String?) {
        msgHelper!!.sendRawPacket(RtuMsg().apply {
            station = "00000"
            text = "anji-jnrq-topic started @ ${LocalDateTime.now()}"
            msgType = RtuMsgType.STATUS.name
        })

        // jnrq TCP
        Thread {
            // test to avoid failure
            val lst = deviceTemplate!!.findJnrqDeviceList()
            lgr.info("device list: {}", JSON.toJSONString(lst.randomOrNull(), true))
            JnrqDecoder(firmId!!, firmCode!!, msgHelper!!)
            JnrqTcpHandler(dataMapper!!, rtuMapper!!, firmId!!, firmCode!!, msgHelper!!, deviceTemplate!!)

            // start serv-packet.
            servPacket(object : ChannelInitializer<SocketChannel>() {
                /**
                 * This method will be called once the [Channel] was registered. After the method returns this instance
                 * will be removed from the [ChannelPipeline] of the [Channel].
                 *
                 * @param ch            the [Channel] which was registered.
                 * @throws Exception    is thrown if an error occurs. In that case it will be handled by
                 * [.exceptionCaught] which will by default close
                 * the [Channel].
                 */
                override fun initChannel(ch: SocketChannel?) {
                    ch!!.pipeline().addLast(JnrqDecoder(firmId!!, firmCode!!, msgHelper!!),
                            JnrqTcpHandler(dataMapper!!, rtuMapper!!, firmId!!, firmCode!!, msgHelper!!, deviceTemplate!!))
                }
            }, firmName!!, portStr!!)
        }.start()

        // jnrq-udp
        Thread() {
            servDatagram(JnrqTcpHandler(dataMapper!!, rtuMapper!!, firmId!!, firmCode!!, msgHelper!!, deviceTemplate!!),
                    firmName!!, portStr!!)
        }.start()

        // san-chuan TCP
        Thread {
            servPacket(object : ChannelInitializer<SocketChannel>() {
                override fun initChannel(ch: SocketChannel?) {
                    ch!!.pipeline().addLast(
                            IdleStateHandler(2, 2, 2),
                            SanchuanDecoder(),
                            SanchuanTcpHandler(dataMapper!!, rtuMapper!!, firmIdYu!!, firmCodeYu!!))
                }
            }, firmNameYu!!, portStrYu!!)
        }.start()

        // san-chuan UDP
        Thread {
            servDatagram(SanchuanUdpHandler(dataMapper!!, rtuMapper!!, firmIdYu!!, firmCodeYu!!), firmNameYu!!,
                    portStrYu!!)
        }.start()

        // Kente TCP
        Thread {
            servPacket(object : ChannelInitializer<SocketChannel>() {
                override fun initChannel(ch: SocketChannel?) {
                    ch!!.pipeline().addLast(KenteDecoder(), KenteTcpHandler(dataMapper!!, rtuMapper!!, firmIdKt!!, firmCodeKt!!))
                }
            }, firmNameKt!!, portStrKt!!)
        }.start()

        // Kente UDP
        Thread {
            servDatagram(KenteUdpHandler(dataMapper!!, rtuMapper!!, firmIdKt!!, firmCodeKt!!), firmNameKt!!, portStrKt!!)
        }.start()

        lgr.info("brand list: {}", dataMapper!!.selectRealtime(DataParam(meterId = "dummy", rows = 1)))

        lgr.info("JNRQ-data-plugin 启动完毕")
    }

    companion object {
        val versionStr = "bw-data-anji(20200728.1)"
        val lock = java.lang.Object()

        val lgr = LoggerFactory.getLogger(BwDataAnjiApplication::class.java)

        /**
         * serve jnrq rtu - prepare for multiple manufacturers.
         */
        fun servPacket(childHandler: ChannelInitializer<SocketChannel>, firmName: String, portStr: String) {
            var boss: NioEventLoopGroup? = null

            var worker: NioEventLoopGroup? = null

            var serverBootstrap: ServerBootstrap? = null

            var future: ChannelFuture? = null

            try {
                boss = NioEventLoopGroup()
                worker = NioEventLoopGroup()
                serverBootstrap = ServerBootstrap()

                serverBootstrap.channel(NioServerSocketChannel::class.java)
                        .group(boss, worker)
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .option(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(childHandler)

                lgr.info("${firmName}TCP Started端口启动中: ${portStr}")
                future = serverBootstrap.bind(portStr.toInt()).sync()

                future!!.channel().closeFuture().sync()
            } catch (ex: Throwable) {
                lgr.error(ex.message, ex)
            } finally {
                boss?.shutdownGracefully()
                worker?.shutdownGracefully()
            }
        }

        /**
         * UDP 服务。
         */
        fun servDatagram(handler: ChannelInboundHandlerAdapter, firmName: String, portStr: String) {
            val group = NioEventLoopGroup()
            try {
                val bootstrap = Bootstrap()
                bootstrap.group(group)
                        .channel(NioDatagramChannel::class.java)
                        .option(ChannelOption.SO_BROADCAST, true)
                        .handler(handler)

                val cf = bootstrap.bind(portStr.toInt()).sync()
                lgr.info("${firmName} UDP服务端口启动中: ${portStr}")

                cf.channel().closeFuture().await()
            } catch (ex: Throwable) {
                lgr.error("启动UDP服务异常: ${ex.message}", ex)
            } finally {
//                group.shutdownGracefully()
            }
        }
    }
}

fun main(args: Array<String>) {
//    runApplication<BwDataAnjiApplication>(*args)
    BwDataAnjiApplication.lgr.info("${BwDataAnjiApplication.versionStr} 启动中...")
    SpringApplication.run(BwDataAnjiApplication::class.java, *args)

    Runtime.getRuntime().addShutdownHook(Thread {
        BwDataAnjiApplication.lock.also {
            synchronized(it) {
                it.notifyAll()
            }
        }
    });

    BwDataAnjiApplication.lock.also {
        synchronized(it) {
            it.wait()
        }
    }

    BwDataAnjiApplication.lgr.info("退出- Got exit signal.")

}
